/**
 * Copyright (c) 2000-2007 Liferay, Inc. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package hr.web.filter.strip;

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * <a href="StripFilter.java.html"><b><i>View Source</i></b></a>
 * 
 * @author Brian Wing Shun Chan
 * 
 */
public class StripFilter implements Filter {

	public static final String ENCODING = "UTF-8";

	private static final String _ALREADY_FILTERED = StripFilter.class
			+ "_ALREADY_FILTERED";

	private static final Logger _log = Logger.getLogger(StripFilter.class
			.getName());

	public void destroy() {
	}

	public void doFilter(final ServletRequest req, final ServletResponse res,
			final FilterChain chain) throws IOException, ServletException {
		if (_log.isLoggable(Level.FINER)) {
			_log.finer("Strip is enabled");
		}

		final HttpServletRequest httpReq = (HttpServletRequest) req;
		final HttpServletResponse httpRes = (HttpServletResponse) res;

		httpReq.setCharacterEncoding(ENCODING);

		final String completeURL = getCompleteURL(httpReq);

		if (/*
			 * isStrip(httpReq) && !isInclude(httpReq) &&
			 */!isAlreadyFiltered(httpReq)) {

			if (_log.isLoggable(Level.FINER)) {
				_log.finer("Stripping " + completeURL);
			}

			httpReq.setAttribute(_ALREADY_FILTERED, Boolean.TRUE);

			final StripResponse stripResponse = new StripResponse(httpRes);

			chain.doFilter(req, stripResponse);

			final String contentType = stripResponse.getContentType();

			final byte[] oldByteArray = stripResponse.getData();

			if ((oldByteArray != null) && (oldByteArray.length > 0)) {
				byte[] newByteArray = new byte[oldByteArray.length];
				int newByteArrayPos = 0;

				if (_log.isLoggable(Level.FINER)) {
					_log.finer("Stripping content of type " + contentType);
				}

				if (contentType.toLowerCase().indexOf("text/") != -1) {
					boolean ignore = false;
					char prevChar = '\n';

					for (int i = 0; i < oldByteArray.length; i++) {
						final byte b = oldByteArray[i];
						final char c = (char) b;

						if (c == '<') {

							// Ignore text inside certain HTML tags.

							if (!ignore) {

								// Check for <pre>

								if ((i + 4) < oldByteArray.length) {
									final char c1 = (char) oldByteArray[i + 1];
									final char c2 = (char) oldByteArray[i + 2];
									final char c3 = (char) oldByteArray[i + 3];
									final char c4 = (char) oldByteArray[i + 4];

									if (((c1 == 'p') || (c1 == 'P'))
											&& ((c2 == 'r') || (c2 == 'R'))
											&& ((c3 == 'e') || (c3 == 'E'))
											&& ((c4 == '>'))) {

										ignore = true;
									}
								}

								// Check for <textarea

								if (!ignore && ((i + 9) < oldByteArray.length)) {

									final char c1 = (char) oldByteArray[i + 1];
									final char c2 = (char) oldByteArray[i + 2];
									final char c3 = (char) oldByteArray[i + 3];
									final char c4 = (char) oldByteArray[i + 4];
									final char c5 = (char) oldByteArray[i + 5];
									final char c6 = (char) oldByteArray[i + 6];
									final char c7 = (char) oldByteArray[i + 7];
									final char c8 = (char) oldByteArray[i + 8];
									final char c9 = (char) oldByteArray[i + 9];

									if (((c1 == 't') || (c1 == 'T'))
											&& ((c2 == 'e') || (c2 == 'E'))
											&& ((c3 == 'x') || (c3 == 'X'))
											&& ((c4 == 't') || (c4 == 'T'))
											&& ((c5 == 'a') || (c5 == 'A'))
											&& ((c6 == 'r') || (c6 == 'R'))
											&& ((c7 == 'e') || (c7 == 'E'))
											&& ((c8 == 'a') || (c8 == 'A'))
											&& ((c9 == ' '))) {

										ignore = true;
									}
								}
							} else if (ignore) {

								// Check for </pre>

								if ((i + 5) < oldByteArray.length) {
									final char c1 = (char) oldByteArray[i + 1];
									final char c2 = (char) oldByteArray[i + 2];
									final char c3 = (char) oldByteArray[i + 3];
									final char c4 = (char) oldByteArray[i + 4];
									final char c5 = (char) oldByteArray[i + 5];

									if (((c1 == '/'))
											&& ((c2 == 'p') || (c2 == 'P'))
											&& ((c3 == 'r') || (c3 == 'R'))
											&& ((c4 == 'e') || (c4 == 'E'))
											&& ((c5 == '>'))) {

										ignore = false;
									}
								}

								// Check for </textarea>

								if (ignore && ((i + 10) < oldByteArray.length)) {

									final char c1 = (char) oldByteArray[i + 1];
									final char c2 = (char) oldByteArray[i + 2];
									final char c3 = (char) oldByteArray[i + 3];
									final char c4 = (char) oldByteArray[i + 4];
									final char c5 = (char) oldByteArray[i + 5];
									final char c6 = (char) oldByteArray[i + 6];
									final char c7 = (char) oldByteArray[i + 7];
									final char c8 = (char) oldByteArray[i + 8];
									final char c9 = (char) oldByteArray[i + 9];
									final char c10 = (char) oldByteArray[i + 10];

									if (((c1 == '/'))
											&& ((c2 == 't') || (c2 == 'T'))
											&& ((c3 == 'e') || (c3 == 'E'))
											&& ((c4 == 'x') || (c4 == 'X'))
											&& ((c5 == 't') || (c5 == 'T'))
											&& ((c6 == 'a') || (c6 == 'A'))
											&& ((c7 == 'r') || (c7 == 'R'))
											&& ((c8 == 'e') || (c8 == 'E'))
											&& ((c9 == 'a') || (c9 == 'A'))
											&& ((c10 == '>'))) {

										ignore = false;
									}
								}
							}
						}

						if ((!ignore)
								&& ((c == '\n') || (c == '\r') || (c == '\t'))) {

							if ((i + 1) == oldByteArray.length) {
							}

							if ((prevChar == '\n') || (prevChar == '\r')) {
							} else {
								if (c != '\t') {
									prevChar = c;
								}

								newByteArray[newByteArrayPos++] = b;
							}
						} else {
							prevChar = c;

							newByteArray[newByteArrayPos++] = b;
						}
					}
				} else {
					newByteArray = oldByteArray;
					newByteArrayPos = oldByteArray.length;
				}

				res.setContentLength(newByteArrayPos);

				final OutputStream os = new BufferedOutputStream(res
						.getOutputStream());

				os.write(newByteArray, 0, newByteArrayPos);

				try {
					if (os != null) {
						os.close();
					}
				} catch (final Exception e) {
					if (_log.isLoggable(Level.WARNING)) {
						_log.log(Level.WARNING, e.getMessage(), e);
					}
				}
			}
		} else {
			if (_log.isLoggable(Level.FINER)) {
				_log.finer("Not stripping " + completeURL);
			}

			chain.doFilter(req, res);
		}
	}

	public void init(final FilterConfig config) {
	}

	private String getCompleteURL(final HttpServletRequest req) {
		StringBuffer completeURL = req.getRequestURL();

		if (completeURL == null) {
			completeURL = new StringBuffer();
		}

		if (req.getQueryString() != null) {
			completeURL.append("?");
			completeURL.append(req.getQueryString());
		}

		return completeURL.toString();
	}

	protected boolean isAlreadyFiltered(final HttpServletRequest req) {
		if (req.getAttribute(_ALREADY_FILTERED) != null) {
			return true;
		} else {
			return false;
		}
	}

	/*
	 * protected boolean isInclude(final HttpServletRequest req) { final String
	 * uri = (String) req
	 * .getAttribute(JavaConstants.JAVAX_SERVLET_INCLUDE_REQUEST_URI);
	 * 
	 * if (uri == null) { return false; } else { return true; } }
	 */

	/*
	 * protected boolean isStrip(final HttpServletRequest req) { if
	 * (!ParamUtil.get(req, _STRIP, true)) { return false; } else { // The
	 * exclusive state is used to stream binary content. // Compressing binary
	 * content through a servlet filter is bad on // performance because the
	 * user will not start downloading the // content until the entire content
	 * is compressed.
	 * 
	 * final boolean action = ParamUtil.getBoolean(req, "p_p_action"); final
	 * String windowState = ParamUtil.getString(req, "p_p_state");
	 * 
	 * if (action && windowState.equals("exclusive")) { return false; } else {
	 * return true; } } }
	 */

}